﻿#include "myserial.h"
#include "widget.h"
#include "crcCheck.h"
#include <QDebug>

/**
 * @brief mySerial(QComboBox *comboBox, QObject *parent) : QObject(parent), combox(comboBox), activeSerialPort(nullptr)
 * @param comboBox
 * @param parent
 * @author yuluo
 * @details 构造函数
 */
mySerial::mySerial(QComboBox *comboBox, QObject *parent) : QObject(parent), combox(comboBox), activeSerialPort(nullptr)
{
    timer = new QTimer(this);

    connect(timer, &QTimer::timeout, this, &mySerial::updateSerialPort);

    /* 500ms更新一次 */
    timer->start(500);

    /* 初始化时先更新一次 */
    updateSerialPort();
}

/*
 * @brief mySerial::openPort(const QString &portName, qint32 baudRate, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, QSerialPort::Parity parity)
 * @param portName 串口名称（如 COM3）
 * @param baudRate 波特率（默认 115200）
 * @param dataBits 数据位（默认 8）
 * @param stopBits 停止位（默认 1）
 * @param parity 校验位（默认 None）
 * @return 是否成功打开
 * @author yuluo
 * @details 打开串口
 */
bool mySerial::openPort(const QString &portName, qint32 baudRate, QSerialPort::DataBits dataBits, QSerialPort::StopBits stopBits, QSerialPort::Parity parity)
{
    /* 先判断当前是否有打开的串口 */
    if(activeSerialPort)
    {
        /* 有打开的串口 先关闭 */
        closeSerialPort();
    }

    /* 创建串口号对象 */
    activeSerialPort = new QSerialPort(this);
    /* 设置串口 */
    activeSerialPort->setPortName(portName);
    /* 设置波特率 */
    activeSerialPort->setBaudRate(baudRate);
    /* 设置数据位 */
    activeSerialPort->setDataBits(dataBits);
    /* 设置停止位 */
    activeSerialPort->setStopBits(stopBits);
    /* 设置校验位 */
    activeSerialPort->setParity(parity);

    /* 打开串口失败 要释放资源 */
    if(!activeSerialPort->open(QIODevice::ReadWrite))
    {
        /* 返回失败 */
#if DEBUG_ENABLE
        qWarning() << "无法打开串口:" << portName;
#endif
        delete activeSerialPort;
        activeSerialPort = nullptr;
        return false;
    }

    /* 打开成功 打印一下调试信息 */
#if DEBUG_ENABLE
    qDebug() << "串口已打开:" << portName << "波特率:" << baudRate << "数据位:" << dataBits << "停止位:" << stopBits << "校验位:" << parity;
#endif

    /* 打开串口成功 连接读取的信号槽 */
    connect(activeSerialPort, &QSerialPort::readyRead, this, &mySerial::onReadRead);

    /* 发射串口打开的信号 */
    emit portOpend(portName);
    return true;
}

/*
 * @brief mySerial::closeSerialPort()
 * @author yuluo
 * @details 关闭当前串口
 */
void mySerial::closeSerialPort()
{
    if(activeSerialPort)
    {
        QString name = activeSerialPort->portName();
        if(activeSerialPort->isOpen())
        {
            activeSerialPort->close();
#if DEBUG_ENABLE
    qDebug() << "串口已关闭:" << name;
#endif
            emit portClosed(name);
        }
        /* 当前可用串口被移除了 需要停止发送 断开接收槽 停止发送定时器 */
        if(retryTimer)
        {
            retryTimer->stop();
            disconnect(retryTimer, &QTimer::timeout ,this, &mySerial::sendFirst);
            delete retryTimer;
            retryTimer = nullptr;
        }
        delete activeSerialPort;
        activeSerialPort = nullptr;

    }
}

/**
 * @brief *mySerial::currentSerialPort()
 * @return 当前使用的串口
 * @author yuluo
 * @details 返回当前使用的串口
 */
QSerialPort *mySerial::currentSerialPort()
{
    return activeSerialPort;
}

/**
 * @brief mySerial::makeDataPacket
 * @param funcCode 功能码
 * @param dataPack 数据包
 * @return 组合好的数据包
 * @author yuluo
 * @details 组合数据包
 */
QByteArray mySerial::makeDataPacket(quint8 funcCode, const QByteArray &dataPack)
{
    QByteArray packet;
    quint32 crcResult = 0xffffffff;

    /* 帧头 0xAA 0xAA */
    packet.append(char(0xAA));
    packet.append(char(0x55));

    /* 数据长度 = 功能字（1） + 数据块（N） + 校验和（4） */
    quint16 length = 1 + dataPack.size() + 4;
    /* 数据长度 高位在前 */
    packet.append(char((length >> 8) & 0xFF));
    packet.append(char(length & 0xFF));

    /* 功能码 */
    packet.append(QByteArray(1, static_cast<char>(funcCode)));

    /* 数据块 */
    packet.append(dataPack);

    /* 把功能码和数据块放到一个数组里 方便计算校验和 */
    QByteArray crcData;
    crcData.append(QByteArray(1, static_cast<char>(funcCode)));
    crcData.append(dataPack);

    /* 计算校验和 */
    if(crcSelect == "CRC-32")
    {
        crcResult = crcCheck::calculate_crc32(crcData);
    }
    else if(crcSelect == "CRC-8")
    {
        crcResult = crcCheck::calculate_crc8(crcData);
    }

    /* 把校验和存放到数组里 */
    packet.append(char((crcResult >> 24) & 0xFF));
    packet.append(char((crcResult >> 16) & 0xFF));
    packet.append(char((crcResult >> 8) & 0xFF));
    packet.append(char(crcResult & 0xFF));

    /* 添加尾帧 */
    packet.append(char(0x55));
    packet.append(char(0xAA));

    /* 返回数据包 */
    return packet;
}

/**
 * @brief mySerial::sendDataPacket
 * @param funcCode
 * @param dataPack
 * @return
 * @author yuluo
 * @details 发送数据包
 */
bool mySerial::sendDataPacket(quint8 funcCode, const QByteArray &dataPack)
{
    if(!activeSerialPort || !activeSerialPort->isOpen())
    {
        return false;
    }
    QByteArray packet = makeDataPacket(funcCode, dataPack);
    return activeSerialPort->write(packet) == packet.size();
}

/**
 * @brief mySerial::sendDataPacketRetry
 * @param sendFuncCode
 * @param ackFuncCode
 * @param dataPack
 * @author yuluo
 * @details 重发数据包
 */
void mySerial::sendDataPacketRetry(quint8 sendFuncCode, quint8 ackFuncCode, const QByteArray &dataPack, const QString crc)
{
    if(!retryTimer)
    {
        retryTimer = new QTimer(this);
        if(!retryTimer) return;
        connect(retryTimer, &QTimer::timeout ,this, &mySerial::sendFirst);
    }

    retryCount = 0;
    waitSendFuncCode = sendFuncCode;
    waitAckFuncCode = ackFuncCode;
    waitSendData = dataPack;
    ackReceivedState = false;
    crcSelect = crc;

    retryTimer->start(500); // 手动启动下一次
    whichTimeStrike = 0;

    sendFirst();
}

/**
 * @brief mySerial::sendDataPacketRetryFrimwareData
 * @param sendFuncCode
 * @param ackFuncCode
 * @param dataPack
 * @author yuluo
 * @details 重发数据包 主要是重发时间不一样 这个等待3s
 */
void mySerial::sendDataPacketRetryFrimwareData(quint8 sendFuncCode, quint8 ackFuncCode, const QByteArray &dataPack, const QString crc)
{
    if(!retryTimer)
    {
        retryTimer = new QTimer(this);
        if(!retryTimer) return;
        connect(retryTimer, &QTimer::timeout ,this, &mySerial::sendFirst);
    }

    retryCount = 0;
    waitSendFuncCode = sendFuncCode;
    waitAckFuncCode = ackFuncCode;
    waitSendData = dataPack;
    ackReceivedState = false;
    crcSelect = crc;

    retryTimer->start(3000); // 手动启动下一次
    whichTimeStrike = 1;

    sendFirst();
}

/**
 * @brief mySerial::setWaitFuncCode(quint8 funcCode)
 * @author yuluo
 * @details 设置需要等待的应答码
 */
void mySerial::setWaitFuncCode(quint8 funcCode)
{
    waitAckFuncCode = funcCode;
}

/**
 * @brief mySerial::updateSerialPort()
 * @author yuluo
 * @details 更新串口
 */
void mySerial::updateSerialPort()
{
    /* 当前可用的串口列表 */
    QStringList currentSerialPorts;
    /* 获取所有的活动串口 */
    for(const QSerialPortInfo &info : QSerialPortInfo::availablePorts())
    {
        /* 保存所有可用的串口 */
        QString portText = info.portName();
        if (!info.description().isEmpty())
        {
            portText += "：" + info.description();  /* 拼接描述信息 */
        }
        currentSerialPorts << portText;
    }

    /* 检测到有新增串口 */
    for(const QString &port : currentSerialPorts)
    {
        /* 原列表没有这个串口 */
        if(!serialPortList.contains(port))
        {
#if DEBUG_ENABLE
    qDebug() << "串口插入:" << port;
#endif
            /* 发射新增串口信号 */
            emit portAdded(port);
        }
    }

    /* 检测移除的串口 */
    for(const QString &port : serialPortList)
    {
        if(!currentSerialPorts.contains(port))
        {
#if DEBUG_ENABLE
    qDebug() << "串口移除:" << port;
#endif
            /* 发射串口移除信号 */
            emit portRemoved(port);

            QString comName = port.section(QRegExp("[：:]"), 0, 0).trimmed();

#if DEBUG_ENABLE
    qDebug() << "comName:" << comName;
#endif

            /* 如果是正在使用的串口 则关闭 */
            if(activeSerialPort && activeSerialPort->portName().compare(comName, Qt::CaseInsensitive) == 0)
            {
#if DEBUG_ENABLE
    qWarning() << "已打开的串口被移除 关闭串口:" << port;
#endif
                disconnect(activeSerialPort, &QSerialPort::readyRead, this, &mySerial::onReadRead);
                /* 发射断开信号 */
                emit portDisconnectde();
//                closeSerialPort();
            }
        }
    }

    /* 更新串口列表 */
    serialPortList = currentSerialPorts;

    refreshComboBox();
}

/**
 * @brief mySerial::onReadRead
 * @author yuluo
 * @details 串口接收信号槽
 */
void mySerial::onReadRead()
{
    QByteArray data = activeSerialPort->readAll();
#if DEBUG_ENABLE
    qDebug() << "收到原始数据:" << data.toHex(' ');
#endif
    rxBuffer.clear();
    /* 把接收的数据缓存到缓冲区 */
    rxBuffer.append(data);
    /* 解析数据包 */
    parseDataPacket();
}

/**
 * @brief mySerial::sendFirst
 * @author yuluo
 * @details 发送1次数据包
 */
void mySerial::sendFirst()
{
    /* 已经接收到应答了就不要发了 */
    if(ackReceivedState)
    {
        retryTimer->stop();
        return;
    }
    if (!retryTimer || !activeSerialPort || !activeSerialPort->isOpen()) {
        return; // 已经关闭，不再发
    }

    /* 重发次数最大3次 */
    if(retryCount < 3)
    {
#if DEBUG_ENABLE
        qDebug() << "发送第" << (retryCount + 1) << "次指令";
#endif
        sendDataPacket(waitSendFuncCode, waitSendData);
        retryCount++;
        if(whichTimeStrike == 0)
        {
            retryTimer->start(500); // 手动启动下一次
        }
        else if (whichTimeStrike == 1)
        {
            retryTimer->start(3000); // 手动启动下一次
        }
    }
    else
    {
        /* 发了3次仍然没有接收到正确的的应答 */
        retryTimer->stop();
#if DEBUG_ENABLE
        qDebug() << "请求 功能码" << QString::number(waitSendFuncCode, 16)
                 << "重发3次仍未收到应答";
#endif
        emit noAckReceived(waitSendFuncCode);
    }
}

/**
 * @brief mySerial::refreshComboBox()
 * @author yuluo
 * @details 刷新显示串口的下拉框
 */
void mySerial::refreshComboBox()
{
    if(!combox)
    {
        return;
    }
    /* 记录当前的串口 */
    QString currentText = combox->currentText();

    /* 移除已经不存在的串口 */
    for (int i = 0; i < combox->count(); )
    {
        QString itemText = combox->itemText(i);
        if (!serialPortList.contains(itemText))
        {
            /* 移除不存在的 */
            combox->removeItem(i);
        }
        else
        {
            /* 保留，检查下一个 */
            ++i;
        }
    }

    /* 添加新的串口 */
    for (const QString &port : serialPortList)
    {
        if (combox->findText(port) == -1)
        {
            /* 添加新串口 */
            combox->addItem(port);
        }
    }

    /* 恢复原来的选择 */
    int index = combox->findText(currentText);
    if (index >= 0)
    {
        combox->setCurrentIndex(index);
    }
    else if (combox->count() > 0)
    {
        /* 默认选第一个 */
        combox->setCurrentIndex(0);
    }
}

/**
 * @brief mySerial::parseDataPacket
 * @author yuluo
 * @details 解析数据包
 */
void mySerial::parseDataPacket()
{
#if DEBUG_ENABLE
    qWarning() << "数据解析中";
#endif
#if DEBUG_ENABLE
    qDebug() << "rxBuffer数据:" << rxBuffer.toHex(' ');
#endif
    /* 头帧 2 + 长度 2 + 功能字 1 + 校验和 2 + 尾帧 2 最少11个字节 */
    if(rxBuffer.size() < 11) return;

    /* 找头帧 */
    if(!(rxBuffer[0] == char(0xAA) && rxBuffer[1] == char(0x55)))
    {
        /* 不是头帧 丢弃前一个字节 */
        rxBuffer.remove(0, 1);
        return;
    }

    /* 获取长度 此处的长度包括 功能字 数据块 校验和 */
    quint16 len = (quint8(rxBuffer[2]) << 8) | quint8(rxBuffer[3]);
    /* 总长度 len + 头 + 尾 + 数据长度 */
    quint16 totalLen = 2 + 2 + len + 2;
    /* 数据不全 */
    if(rxBuffer.size() < totalLen) return;

    /* 取出一个完整的数据包 */
    QByteArray packet = rxBuffer.left(totalLen);
    rxBuffer.remove(0, totalLen);

    /* 检查尾帧 */
    if(!(packet.endsWith("\x55\xAA")))
    {
#if DEBUG_ENABLE
        qWarning() << "尾帧错误";
#endif
        return;
    }

    /* 把头帧和长度这4个字节去掉 */
    QByteArray packetBody = packet.mid(4, len);
    /* 取出功能字 */
    quint8 funcCode = quint8(packetBody[0]);
    /* 删除功能字 */
    QByteArray dataPacket = packetBody.left(packetBody.size() - 4).mid(1);
    /* 取出校验和 */
    QByteArray crcBits = packetBody.right(4);

    quint32 recCrc = (quint8(crcBits[0]) << 24) |
                     (quint8(crcBits[1]) << 16) |
                     (quint8(crcBits[2]) << 8)  |
                      quint8(crcBits[3]);

    quint32 calCrc = 0xffffffff;
    if(crcSelect == "CRC-32")
    {
        calCrc = crcCheck::calculate_crc32(packetBody.left(packetBody.size() - 4));
    }
    else if(crcSelect == "CRC-8")
    {
        calCrc = crcCheck::calculate_crc8(packetBody.left(packetBody.size() - 4));
    }

#if DEBUG_ENABLE
    qWarning() << QString::number(calCrc, 16);
#endif
    if(recCrc != calCrc)
    {
#if DEBUG_ENABLE
        qWarning() << "CRC 错误";
#endif
        ackReceivedState = true;
        if(retryTimer)
        {
            retryTimer->stop();
        }
        emit checksumError(funcCode);
        return;
    }

    /* 把数据传到上层 */
    handleReceivedPacket(funcCode, dataPacket);
}

/**
 * @brief mySerial::handleReceivedPacket
 * @param funcCode
 * @param data
 */
void mySerial::handleReceivedPacket(quint8 funcCode, const QByteArray &data)
{
#if DEBUG_ENABLE
    qDebug() << "收到数据包 应答码:" << QString::number(funcCode, 16)
                 << " 数据:" << data.toHex(' ');
#endif
#if DEBUG_ENABLE
    qDebug() << "waitAckFuncCode:" << QString::number(waitAckFuncCode, 16);
#endif
    if(funcCode == waitAckFuncCode)
    {
        ackReceivedState = true;
        if(retryTimer)
        {
            retryTimer->stop();
        }
        emit ackReceived(funcCode, data);
    }
}


